#include "Camera.h"

Camera::Camera(HWND windowHandle):
	maxRho(10.0f),
	minRho(1.0f),
	moving(false),
	phi(DirectX::XM_PIDIV2),
	radsPerX(0.01f),
	radsPerY(0.01f),
	rho(1.0f),
	rhosPerDelta(1.0f),
	theta(-DirectX::XM_PIDIV2),
	windowHandle(windowHandle)
{
}

DirectX::XMMATRIX Camera::ViewMatrix() const
{
	DirectX::XMFLOAT3 focus(0.0f, 0.0f, 0.0f);

	DirectX::XMFLOAT3 up;
	if (phi <= DirectX::XM_PIDIV2)
	{
		up = DirectX::XMFLOAT3(
			-DirectX::XMScalarCos(theta)*DirectX::XMScalarCos(phi),
			DirectX::XMScalarSin(phi),
			-DirectX::XMScalarSin(theta)*DirectX::XMScalarCos(phi));
	}
	else if (phi <= 3.0f * DirectX::XM_PIDIV4)
	{
		up = DirectX::XMFLOAT3(
			DirectX::XMScalarCos(theta)*DirectX::XMScalarCos(phi),
			DirectX::XMScalarSin(phi),
			DirectX::XMScalarSin(theta)*DirectX::XMScalarCos(phi));
	}
	else
	{
		up = DirectX::XMFLOAT3(
			-DirectX::XMScalarCos(theta)*DirectX::XMScalarCos(phi),
			-DirectX::XMScalarSin(phi),
			-DirectX::XMScalarSin(theta)*DirectX::XMScalarCos(phi));
	}

	DirectX::XMFLOAT3 eye(
		rho * DirectX::XMScalarSin(phi) * DirectX::XMScalarCos(theta),
		rho * DirectX::XMScalarCos(phi),
		rho * DirectX::XMScalarSin(phi) * DirectX::XMScalarSin(theta));

	return DirectX::XMMatrixLookAtLH(
		DirectX::XMLoadFloat3(&eye),
		DirectX::XMLoadFloat3(&focus),
		DirectX::XMLoadFloat3(&up));
}

void Camera::OnMouseMove(int x, int y)
{
	if (moving)
	{
		// Update angles based on cursor delta
		theta -= radsPerX * (x - prevClickX);
		phi -= radsPerY * (y - prevClickY);

		// Set new previous values
		prevClickX = x;
		prevClickY = y;

		// Clamp theta to [-Pi,Pi]
		if (theta > DirectX::XM_PI) theta -= 2.0f * DirectX::XM_PI;
		else if (theta < -DirectX::XM_PI) theta += 2.0f * DirectX::XM_PI;

		// Clamp phi to [0,7Pi/16]
		if (phi > 7.0f * DirectX::XM_PI / 16.0f) phi = 7.0f * DirectX::XM_PI / 16.0f;
		else if (phi < 0.0f) phi = 0.0f;
	}
}

void Camera::OnMouseWheel(int x, int y, float delta)
{
	// Update rho based on mouse wheel delta
	rho -= rhosPerDelta * delta;

	// Clamp rho to [minRho,maxRho]
	if (rho > maxRho) rho = maxRho;
	else if (rho < minRho) rho = minRho;
}

void Camera::OnRightMouseDown(int x, int y)
{
	// "Lock" cursor upon right click
	moving = true;

	// Capture all cursor movements, even outside window
	SetCapture(windowHandle);

	// Hide cursor
	ShowCursor(FALSE);

	// Save original cursor position
	prevClickX = refClickX = x;
	prevClickY = refClickY = y;
}

void Camera::OnRightMouseUp(int x, int y)
{
	if (moving)
	{
		// "Unlock" cursor when right click is lifted
		moving = false;

		// Stop capturing all cursor movement
		ReleaseCapture();

		// Make cursor visible
		ShowCursor(TRUE);

		// Restore original cursor position
		POINT p = {refClickX, refClickY};
		ClientToScreen(windowHandle, &p);
		SetCursorPos(p.x, p.y);
	}
}